package com.example.demo.lucene.bank;

import com.alibaba.fastjson.JSONObject;
import com.example.demo.lucene.bank.mapper.BDBankArchivesMapper;
import com.example.demo.lucene.bank.mapper.BankNewInfoMapper;
import com.example.demo.lucene.bank.po.BDBankArchives;
import com.example.demo.lucene.bank.po.BDBankArchivesExample;
import com.example.demo.lucene.bank.po.BankNewInfo;
import com.example.demo.lucene.bank.po.BankNewInfoExample;
import com.example.demo.lucene.book.MyIKAnalyzer;
import com.example.demo.util.DateTimeUtil;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.StringField;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.*;
import org.apache.lucene.queryparser.classic.MultiFieldQueryParser;
import org.apache.lucene.queryparser.classic.ParseException;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.search.highlight.*;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 * https://blog.csdn.net/lovely960823/article/details/110046111?spm=1001.2101.3001.6650.1&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7ERate-1-110046111-blog-132048047.235%5Ev38%5Epc_relevant_sort_base2&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7ERate-1-110046111-blog-132048047.235%5Ev38%5Epc_relevant_sort_base2&utm_relevant_index=2
 */
@Controller
@RequestMapping("bank")
public class BankLuceneDiskController {
    @Resource
    private BDBankArchivesMapper bdBankArchivesMapper;
    @Resource
    private BankNewInfoMapper bankNewInfoMapper;
    private IndexWriter indexWriter;
    private static final String index_dir="d:\\indexDir\\bankinfo";
    @RequestMapping("disk")
    public String home(HttpServletRequest request) throws IOException {
        Directory directory = FSDirectory.open(FileSystems.getDefault().getPath(index_dir));
        // 索引读取工具
        IndexReader reader = DirectoryReader.open(directory);
        int numDocs = reader.numDocs();
        request.setAttribute("total",numDocs);
        return "bankDisk";
    }
    public BankLuceneDiskController() throws IOException {
        initIndex();
        // 索引目录类,指定索引在硬盘中的位置，我的设置为D盘的indexDir文件夹
        Directory directory = FSDirectory.open(FileSystems.getDefault().getPath(index_dir));
        // 引入IK分词器
        Analyzer analyzer = new MyIKAnalyzer();
        // 索引写出工具的配置对象，这个地方就是最上面报错的问题解决方案
        IndexWriterConfig conf = new IndexWriterConfig(analyzer);
        // 设置打开方式：OpenMode.APPEND 会在索引库的基础上追加新索引。OpenMode.CREATE会先清空原来数据，再提交新的索引
        conf.setOpenMode(IndexWriterConfig.OpenMode.CREATE_OR_APPEND);
        // 创建索引的写出工具类。参数：索引的目录和配置信息
        indexWriter = new IndexWriter(directory, conf);
    }
    public void initIndex() throws IOException{
        // 索引目录类,指定索引在硬盘中的位置，我的设置为D盘的indexDir文件夹
        Directory directory = FSDirectory.open(FileSystems.getDefault().getPath(index_dir));
        String[] strings = directory.listAll();
        if(strings==null || strings.length>0){
            //已经初始化的不执行
            return;
        }
        // 引入IK分词器
        Analyzer analyzer = new MyIKAnalyzer();
        // 索引写出工具的配置对象，这个地方就是最上面报错的问题解决方案
        IndexWriterConfig conf = new IndexWriterConfig(analyzer);
        // 设置打开方式：OpenMode.APPEND 会在索引库的基础上追加新索引。OpenMode.CREATE会先清空原来数据，再提交新的索引
        conf.setOpenMode(IndexWriterConfig.OpenMode.CREATE);
        // 创建索引的写出工具类。参数：索引的目录和配置信息
        IndexWriter indexWriterCreate = new IndexWriter(directory, conf);
        BankNewInfo bank=new BankNewInfo();
        bank.setBankId("0");
        bank.setBankNo("0");
        bank.setBankCode("0");
        bank.setBankName("0");
        Document document1 = generateBankDoc(bank);
        indexWriterCreate.addDocument(document1);
        indexWriterCreate.commit();
        indexWriterCreate.close();
    }
    @ResponseBody
    @RequestMapping("/createIndex")
    public JSONObject createIndex() throws IOException{
        long startTime=System.currentTimeMillis();
        JSONObject jsonObject1=createIndex1();
        JSONObject jsonObject2=createIndex2();
        JSONObject jsonObject=new JSONObject();
        long costTime=System.currentTimeMillis()-startTime;
        jsonObject.put("costTime",costTime);
        long total1=jsonObject1.getLong("total");
        long total2=jsonObject2.getLong("total");
        long total=total1+total2;
        jsonObject.put("total",total);
        return jsonObject;
    }
    @ResponseBody
    @RequestMapping("/createIndex1")
    public JSONObject createIndex1() throws IOException {
        JSONObject jsonObject=new JSONObject();
        long startTime=System.currentTimeMillis();

        Integer pageNum = 1;
        Integer pageSize = 20000;
        String orderBy = "BRB_BANKNO asc";
        BDBankArchivesExample example=new BDBankArchivesExample();
        PageHelper.startPage(pageNum,pageSize,orderBy);
        List<BDBankArchives> list = bdBankArchivesMapper.selectByExample(example);
        long total = new PageInfo(list).getTotal();
        System.out.println("总记录数："+total);
        List<BankNewInfo> list1 = getArchiveBank(list);
        long remainNum=total;
        do{
            batchSave(list1);
            remainNum-=pageSize;
            System.out.println("pageNum:"+pageNum+",time:"+ DateTimeUtil.longToStr(System.currentTimeMillis()));
            pageNum++;
            PageHelper.startPage(pageNum,pageSize,orderBy);
            list = bdBankArchivesMapper.selectByExample(example);
            list1 = getArchiveBank(list);
        }while (remainNum>0);
        // 关闭
        long costTime=System.currentTimeMillis()-startTime;
        System.out.println("索引创建完毕，耗时"+costTime+"ms");
        jsonObject.put("costTime",costTime);
        jsonObject.put("total",total);
        return jsonObject;
    }

    private static List<BankNewInfo> getArchiveBank(List<BDBankArchives> list) {
        List<BankNewInfo> list1=new ArrayList<>(list.size());
        list.forEach(bank->{
            BankNewInfo bankNewInfo=new BankNewInfo();
            bankNewInfo.setBankCode(bank.getArchivesCode());
            bankNewInfo.setBankName(bank.getArchivesName());
            bankNewInfo.setBankNo(bank.getBrbBankno());
            bankNewInfo.setBankId(bank.getBankArchivesId());
            list1.add(bankNewInfo);
        });
        return list1;
    }
    @ResponseBody
    @RequestMapping("/createIndex2")
    public JSONObject createIndex2() throws IOException {
        JSONObject jsonObject=new JSONObject();
        long startTime=System.currentTimeMillis();
        BankNewInfoExample example=new BankNewInfoExample();
        Integer pageNum = 1;
        Integer pageSize = 20000;
        String orderBy = "bank_no asc";
        PageHelper.startPage(pageNum,pageSize,orderBy);
        List<BankNewInfo> list1 = bankNewInfoMapper.selectByExample(example);
        long total = new PageInfo(list1).getTotal();
        System.out.println("总记录数："+total);
        long remainNum=total;


        do{
            batchSave(list1);
            remainNum-=pageSize;
            System.out.println("pageNum:"+pageNum+",time:"+ DateTimeUtil.longToStr(System.currentTimeMillis()));
            pageNum++;
            PageHelper.startPage(pageNum,pageSize,orderBy);
            list1 = bankNewInfoMapper.selectByExample(example);

        }while (remainNum>0);
        long costTime=System.currentTimeMillis()-startTime;
        System.out.println("索引创建完毕，耗时"+costTime+"ms");
        jsonObject.put("costTime",costTime);
        jsonObject.put("total",total);
        jsonObject.put("index_dir",index_dir);
        return jsonObject;
    }

    private void batchSave(List<BankNewInfo> list1) throws IOException {
        // 创建文档的集合
        Collection<Document> docs = new ArrayList<>();
        list1.forEach(bank-> {
            //contentMapper.insertSelective(list1.get(i));
            Document document1 = generateBankDoc(bank);
            docs.add(document1);
        });
        // 把文档集合交给IndexWriter
        indexWriter.addDocuments(docs);
        // 提交
        indexWriter.commit();
    }

    @NotNull
    private Document generateBankDoc(BankNewInfo bank) {
        // 创建文档对象
        Document document1 = new Document();
        //StringField会创建索引，但是不会被分词，TextField，即创建索引又会被分词。
        document1.add(new StringField("bankId", bank.getBankId(), Field.Store.YES));

        document1.add(new StringField("bankNo", bank.getBankNo(), Field.Store.YES));
        document1.add(new TextField("bankName", bank.getBankName(), Field.Store.YES));
        document1.add(new StringField("bankCode", bank.getBankCode(), Field.Store.YES));
        return document1;
    }


    private TopDocs searchByPage(int page,int perPage, IndexSearcher searcher, Query query) throws IOException {
        TopDocs result = null;
        if(query == null){
            System.out.println(" Query is null return null ");
            return null;
        }
        ScoreDoc before = null;
        if(page != 1){
            TopDocs docsBefore = searcher.search(query, (page-1)*perPage);
            ScoreDoc[] scoreDocs = docsBefore.scoreDocs;
            if(scoreDocs.length > 0){
                before = scoreDocs[scoreDocs.length - 1];
            }
        }
        result = searcher.searchAfter(before, query, perPage);
        return result;
    }
    @GetMapping("search")
    @ResponseBody
    public JSONObject search(String text) throws InvalidTokenOffsetsException, IOException, ParseException {
        JSONObject jsonObject=new JSONObject();
        long start=System.currentTimeMillis();
        int page=1;
        int pageSize=10;
        Directory directory = FSDirectory.open(FileSystems.getDefault().getPath(index_dir));
        // 索引读取工具
        IndexReader reader = DirectoryReader.open(directory);
        // 索引搜索工具
        IndexSearcher searcher = new IndexSearcher(reader);
        // 创建查询解析器,两个参数：默认要查询的字段的名称，分词器
        MultiFieldQueryParser  parser = new MultiFieldQueryParser(str, new MyIKAnalyzer());
        // 创建查询对象
        Query query = parser.parse(text);
        // 获取前十条记录
        //TopDocs topDocs = searcher.search(query, 100);

        TopDocs topDocs = searchByPage(page,pageSize,searcher,query);
        // 获取总条数
        System.out.println("本次搜索共找到" + topDocs.totalHits + "条数据");

        //高亮显示
        SimpleHTMLFormatter simpleHTMLFormatter = new SimpleHTMLFormatter("<span style='color:red'>", "</span>");
        Highlighter highlighter = new Highlighter(simpleHTMLFormatter, new QueryScorer(query));
        Fragmenter fragmenter = new SimpleFragmenter(100);   //高亮后的段落范围在100字内
        highlighter.setTextFragmenter(fragmenter);

        // 获取得分文档对象（ScoreDoc）数组.SocreDoc中包含：文档的编号、文档的得分
        ScoreDoc[] scoreDocs = topDocs.scoreDocs;
        List<BankNewInfo> list = new ArrayList<>();
        for (ScoreDoc scoreDoc : scoreDocs) {
            // 取出文档编号
            int docID = scoreDoc.doc;
            // 根据编号去找文档
            Document doc = reader.document(docID);
//            BankNewInfo bankNewInfo = bankNewInfoMapper.selectByPrimaryKey(doc.get("bankId"));
            BankNewInfo bankNewInfo=new BankNewInfo();
            //处理高亮字段显示
            String bankName = highlighter.getBestFragment(new MyIKAnalyzer(), "bankName",doc.get("bankName"));
            bankNewInfo.setBankName(bankName);
            bankNewInfo.setBankId(doc.get("bankId"));
            bankNewInfo.setBankNo(doc.get("bankNo"));
            list.add(bankNewInfo);
        }
        long costTime=System.currentTimeMillis()-start;
        System.out.println("查询耗时:"+costTime+"ms");
        jsonObject.put("list",list);
        jsonObject.put("totalHits",topDocs.totalHits);
        jsonObject.put("costTime",costTime);
        return jsonObject;
    }
    @RequestMapping("/searchText3")
    public String searchText3(String text,HttpServletRequest request) throws IOException,ParseException, InvalidTokenOffsetsException{
        int page=1;
        int pageSize=10;
        Directory directory = FSDirectory.open(FileSystems.getDefault().getPath(index_dir));
        // 索引读取工具
        IndexReader reader = DirectoryReader.open(directory);
        // 索引搜索工具
        IndexSearcher searcher = new IndexSearcher(reader);
        // 创建查询解析器,两个参数：默认要查询的字段的名称，分词器
        MultiFieldQueryParser  parser = new MultiFieldQueryParser(str, new MyIKAnalyzer());
        // 创建查询对象
        Query query = parser.parse(text);
        // 获取前十条记录
        //TopDocs topDocs = searcher.search(query, 100);

        TopDocs topDocs = searchByPage(page,pageSize,searcher,query);
        // 获取总条数
        System.out.println("本次搜索共找到" + topDocs.totalHits + "条数据");

        //高亮显示
        SimpleHTMLFormatter simpleHTMLFormatter = new SimpleHTMLFormatter("<span style='color:red'>", "</span>");
        Highlighter highlighter = new Highlighter(simpleHTMLFormatter, new QueryScorer(query));
        Fragmenter fragmenter = new SimpleFragmenter(100);   //高亮后的段落范围在100字内
        highlighter.setTextFragmenter(fragmenter);

        // 获取得分文档对象（ScoreDoc）数组.SocreDoc中包含：文档的编号、文档的得分
        ScoreDoc[] scoreDocs = topDocs.scoreDocs;
        List<BankNewInfo> list = new ArrayList<>();
        for (ScoreDoc scoreDoc : scoreDocs) {
            // 取出文档编号
            int docID = scoreDoc.doc;
            // 根据编号去找文档
            Document doc = reader.document(docID);
//            BankNewInfo bankNewInfo = bankNewInfoMapper.selectByPrimaryKey(doc.get("bankId"));
            BankNewInfo bankNewInfo=new BankNewInfo();
            //处理高亮字段显示
            String bankName = highlighter.getBestFragment(new MyIKAnalyzer(), "bankName",doc.get("bankName"));
//            if(bankName==null){
//                bankName=bankNewInfo.getBankName();
//            }
            bankNewInfo.setBankName(bankName);
            bankNewInfo.setBankNo(doc.get("bankNo"));
            bankNewInfo.setBankId(doc.get("bankId"));
            list.add(bankNewInfo);
        }
        System.err.println("list的长度："+list.size());
        request.setAttribute("page", page);
        request.setAttribute("pageSize", pageSize);
        request.setAttribute("list",list);
        return "bankDisk";
    }
    private String[] str={"bankName"};
    @RequestMapping("/searchText4")
    public String searchText4(String text,HttpServletRequest request) throws IOException,ParseException, InvalidTokenOffsetsException{
        int page=1;
        int pageSize=100;
        IndexSearcher searcher = getMoreSearch(index_dir);
        // 创建查询解析器,两个参数：默认要查询的字段的名称，分词器
        MultiFieldQueryParser  parser = new MultiFieldQueryParser(str, new MyIKAnalyzer());
        // 创建查询对象
        Query query = parser.parse(text);

        TopDocs topDocs = searchByPage(page,pageSize,searcher,query);
        // 获取总条数
        System.out.println("本次搜索共找到" + topDocs.totalHits + "条数据");

        //高亮显示
        SimpleHTMLFormatter simpleHTMLFormatter = new SimpleHTMLFormatter("<span style='color:red'>", "</span>");
        Highlighter highlighter = new Highlighter(simpleHTMLFormatter, new QueryScorer(query));
        Fragmenter fragmenter = new SimpleFragmenter(100);   //高亮后的段落范围在100字内
        highlighter.setTextFragmenter(fragmenter);

        // 获取得分文档对象（ScoreDoc）数组.SocreDoc中包含：文档的编号、文档的得分
        ScoreDoc[] scoreDocs = topDocs.scoreDocs;
        List<BankNewInfo> list = new ArrayList<>();
        for (ScoreDoc scoreDoc : scoreDocs) {
            // 取出文档编号
            int docID = scoreDoc.doc;
            // 根据编号去找文档
            //Document doc = reader.document(docID);
            Document doc = searcher.doc(docID);//多索引找文档要用searcher找了，reader容易报错
            BankNewInfo bankNewInfo = bankNewInfoMapper.selectByPrimaryKey(doc.get("bankId"));
            //处理高亮字段显示
            String bankName = highlighter.getBestFragment(new MyIKAnalyzer(), "bankName",doc.get("bankName"));
            if(bankName==null){
                bankName=bankNewInfo.getBankName();
            }
            bankNewInfo.setBankName(bankName);
            list.add(bankNewInfo);
        }
        System.err.println("list的长度："+list.size());
        request.setAttribute("page", page);
        request.setAttribute("pageSize", pageSize);
        request.setAttribute("list",list);
        return "bankDisk";
    }

    private IndexSearcher getMoreSearch(String string) {
        MultiReader reader = null;
        //设置
        try {
            File[] files = new File(string).listFiles();

            IndexReader[] readers = new IndexReader[files.length];
            for (int i = 0 ; i < files.length ; i ++) {
                readers[i] = DirectoryReader.open(FSDirectory.open(Paths.get(files[i].getPath(), new String[0])));
            }
            reader = new MultiReader(readers);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return new IndexSearcher(reader);
        //如果索引文件过多，可以这样加快效率
        /**
         ExecutorService service = Executors.newCachedThreadPool();
         return new IndexSearcher(reader,service);
         */
    }
    @RequestMapping("/deleteIndex")
    @ResponseBody
    public JSONObject deleteIndex() throws IOException{
        JSONObject jsonObject=new JSONObject();
        long startTime=System.currentTimeMillis();
        // 创建目录对象
        Directory directory = FSDirectory.open(FileSystems.getDefault().getPath(index_dir));
        String[] strings = directory.listAll();
        for (String fileName:strings){
            directory.deleteFile(fileName);
        }
        long costTime=System.currentTimeMillis()-startTime;
        System.out.println("索引删除完毕，耗时"+costTime+"ms");
        jsonObject.put("costTime",costTime);
        jsonObject.put("index_dir",index_dir);
        return jsonObject;
    }
}
